// 模版编译

// 订阅者
import Watcher from "./watcher";

export default class Compiler {
    constructor(context) {
        // console.log('模板编译', context);
        this.$el = context.$el;
        this.context = context;
        if (this.$el) {
            // 把原始的DOM转换为文档片段
            this.$fragment = this.nodeToFragment(this.$el);
            // console.log('this.$fragment', this.$fragment);
            // 编译模板
            this.compiler(this.$fragment);
            // 把文档片段添加到页面中
            this.$el.appendChild(this.$fragment);
        }
    }

    /**
     * 模板编译
     * @param node
     */
    compiler(node) {
        if (node.childNodes && node.childNodes.length) {
            node.childNodes.forEach(child => {
                // console.log('compiler(node)', child);
                if (child.nodeType === 1) {
                    // console.log('编译元素', child.nodeType);
                    // 当nodeType = 1 时,表示这是一个元素节点
                    this.compilerElementNode(child);
                } else if (child.nodeType === 3) {
                    // console.log('编译文本', child.nodeType);
                    // 当nodeType = 3 时,表示这是一个文本节点
                    this.compilerTextNode(child);
                }
            });
        }
    }

    /**
     * 编译元素节点
     * @param node
     */
    compilerElementNode(node) {
        let that = this;
        // TODO 完成元素的编译
        // TODO 编译指令
        // 获取所有指令
        let attrs = [...node.attributes];
        // console.log(attrs);
        // 递归
        attrs.forEach(attr => {
            // console.log(attr);
            let {name: attrName, value: attrValue} = attr; // 拿到指令的key-value
            // console.log(attrName);
            // console.log(attrValue);
            // TODO 数据指令
            if (attrName.indexOf("v-") === 0) {
                let dirName = attrName.slice(2); // 截取指令名
                switch (dirName) {
                    // 指令判断
                    case 'text':
                        new Watcher(attrValue, this.context, newValue => {
                            node.textContent = newValue;
                        });
                        break;
                    case 'model':
                        new Watcher(attrValue, this.context, newValue => {
                            node.value = newValue;
                        });
                        node.addEventListener("input", e => {
                            that.context[attrValue] = e.target.value;
                        });
                        break;
                }
            }
            // TODO 函数指令
            if (attrName.indexOf("@") === 0) {
                this.compilerMethods(this.context, node, attrName, attrValue);
            }
        })
        this.compiler(node);
    }

    /**
     * 函数编译
     * @param scope
     * @param node
     * @param attrName
     * @param attrValue
     */
    compilerMethods(scope, node, attrName, attrValue) {
        // console.log(scope);
        // console.log(attrValue);
        // 获取类型
        let type = attrName.slice(1);
        // 获取函数体 - 在index.js中已经做过函数代理,此时函数就在作用域中
        let fn = scope[attrValue];
        // 给节点添加函数
        node.addEventListener(type, fn.bind(scope));
    }

    /**
     * 编译文本节点
     * @param node
     */
    compilerTextNode(node) {
        let text = node.textContent.trim();
        if (text) {
            // 把text字符串转换为表达式
            let exp = this.parseTextExp(text);
            // console.log(exp);
            // 添加订阅者,计算表达式的值;
            // 当表达式依赖的数据发生变化时:
            // 1. 重新计算表达式的值 2. 最新的值赋给node.textContent
            // 这样即可完成Model -> View的响应式
            new Watcher(exp, this.context, (newValue) => {
                node.textContent = newValue;
            });
        }
    }

    /**
     * 文本到表达式的转换
     * 111-{{msg + ' Vue'}}-222
     * "111-" + msg + "Vue" + "-222"
     * @param text
     */
    parseTextExp(text) {
        // 匹配插值表达式正则
        let regText = /\{\{(.+?)\}\}/g;
        // 分割插值表达式前后内容
        let pices = text.split(regText);
        // console.log('转换parseTextExp(text)', pices);
        // 匹配插值表达式
        let matches = text.match(regText);
        // console.log(matches);
        // 表达式数组
        let tokens = [];
        pices.forEach(item => {
            if (matches && matches.indexOf("{{" + item + "}}" > -1)) {
                tokens.push("(" + item + ")");
            } else {
                tokens.push("`" + item + "`");
            }
        })
        return tokens.join("+");
    }

    /**
     * 把所有的元素转换为文档片段
     * @param node
     */
    nodeToFragment(node) {
        // DocumentFragments 是DOM节点。它们不是主DOM树的一部分。通常的用例是创建文档片段，将元素附加到文档片段，然后将文档片段附加到DOM树。在DOM树中，文档片段被其所有的子元素所代替。
        // 因为文档片段存在于内存中，并不在DOM树中，所以将子元素插入到文档片段时不会引起页面回流（对元素位置和几何上的计算）。因此，使用文档片段通常会带来更好的性能。
        // 使用appendChild方法dom树的节点添加到DF中时，会删除原来的节点
        // console.log('nodeToFragment', node);
        let fragment = document.createDocumentFragment();
        // console.log('node.childNodes', node.childNodes);
        if (node.childNodes && node.childNodes.length) {
            node.childNodes.forEach(child => {
                // console.log('child', child);
                // 判断是否为需要添加的节点, 如果是注释或者无用的换行则不添加
                if (!this.igorable(child)) {
                    fragment.appendChild(child);
                }
            });
        }
        return fragment;
    }

    /**
     * 忽略节点, 被忽略的节点不添加进文档片段中
     * @param node
     */
    igorable(node) {
        const reg = /^[\t\n\r]+/;
        // 8 - 注释   3 - 文本
        return (node.nodeType === 8 || (node.nodeType === 3 && reg.test(node.textContent)))
    }
}
